import csv
import math
import os
from datetime import datetime

from openpyxl import Workbook
from simpy import Environment

from osc01.optimization.get_cost import get_cost_with_simulation
from osc01.resource.MonitoredFilterStore import MonitoredFilterStore
from osc01.resource.Resources import ResourceUtil
from osc01.resource.stakeholders import ConstructionSite
from osc01.static.constants import Constants, Debugs
from osc01.utils.cpm_util import get_schedule
from osc01.utils.random_util import RandomUtil


def get_cost_with_simulation_on_seed(start_correction,
                                     seed=None,
                                     unit_waiting_penalty=Debugs.unit_component_late_acceptance_penalty_by_day,
                                     unit_late_delivery_penalty=Debugs.unit_late_delivery_penalty_by_day,
                                     unit_project_late_completion_penalty=Debugs.unit_project_late_completion_penalty_by_day,
                                     project_deadline=Debugs.project_deadline,
                                     debug=False
                                     ):
    c_t = datetime.now()

    # print('delivery_time_offset=', delivery_time_offset)
    activities, prj_start_date = get_schedule()
    print("prj_start_date=", prj_start_date)
    # scenario_num = 20
    sum_additional_cost_every_scenario = 0
    sum_additional_cost_every_scenario_in_detail = [0, 0, 0]
    if debug:
        with open('x_and_cost.csv', 'a', newline='') as records:
            writer = csv.writer(records)
            writer.writerow(["Date", "Hr", "Min", "total_waiting_cost", "total_late_delivery_penalty",
                             "project_late_completion_penalty", "average cost",
                             "FL01", "FL02", "FL03", "FL04", "FL05", "FL06", "FL07",
                             "FL08", "FL09", "FL10", "FL11", "FL12", "FL13", "FL14",
                             "FL15", "FL16", "FL17", "FL18"])
            # writer.writerow(start_correction)
            # writer.writerow(['Scenario no', 'Additional cost'])
            # for i in range(scenario_num):
            # get
            total_waiting_cost, total_late_delivery_penalty, project_late_completion_penalty \
                = simulate(activities, debug, project_deadline, seed, start_correction, unit_late_delivery_penalty,
                           unit_project_late_completion_penalty, unit_waiting_penalty)
            total = total_waiting_cost - total_late_delivery_penalty + project_late_completion_penalty
            # print("Debug=True, seed=", i, "\ttotal=", total)
            # writer.writerow([c_t.strftime("%y-%m-%d"), c_t.strftime("%H"), c_t.strftime("%M"),
            #                  seed, total_waiting_cost,
            #                  -total_late_delivery_penalty,
            #                  project_late_completion_penalty,
            #                  total])
            # sum the detailed total cost from scenarios
            sum_additional_cost_every_scenario_in_detail = [m + n for m, n in
                                                            zip(sum_additional_cost_every_scenario_in_detail,
                                                                [total_waiting_cost, total_late_delivery_penalty,
                                                                 project_late_completion_penalty])]

            average_cost = (sum_additional_cost_every_scenario_in_detail[0] -
                            sum_additional_cost_every_scenario_in_detail[1] +
                            sum_additional_cost_every_scenario_in_detail[2])

            cost_ = [c_t.strftime("%y-%m-%d"), c_t.strftime("%H"), c_t.strftime("%M"),
                     sum_additional_cost_every_scenario_in_detail[0],
                     -sum_additional_cost_every_scenario_in_detail[1],
                     sum_additional_cost_every_scenario_in_detail[2],
                     average_cost]
            cost_.extend(start_correction)
            writer.writerow(cost_)
            print("Debug=True, average total=", average_cost)

        return sum_additional_cost_every_scenario_in_detail[0], \
               sum_additional_cost_every_scenario_in_detail[1], \
               sum_additional_cost_every_scenario_in_detail[2]
    else:
        additional_cost = simulate(activities, debug, project_deadline, seed, start_correction,
                                   unit_late_delivery_penalty,
                                   unit_project_late_completion_penalty, unit_waiting_penalty)
        # print("seed= ", i, "cost= ", additional_cost)
        sum_additional_cost_every_scenario += additional_cost
    # print("debug=False, average cost=", sum_additional_cost_every_scenario / scenario_num)
    # return sum_additional_cost_every_scenario / scenario_num
    return sum_additional_cost_every_scenario


def simulate(activities, debug, project_deadline, seed, start_correction, unit_late_delivery_penalty,
             unit_project_late_completion_penalty, unit_waiting_penalty):
    env = Environment()
    # storage can hold 200 components
    storage_yard = MonitoredFilterStore(env, capacity=200)
    # 1 worker team
    assembly_worker_teams = ResourceUtil.get_stations(env, capacity=1)
    # grout_worker_teams = ResourceUtil.get_stations(env, capacity=1)
    grout_gears = ResourceUtil.get_stations(env, capacity=1)
    construction_site1 = ConstructionSite(env=env, name='Site1',
                                          storage_yard=storage_yard,
                                          worker_teams=assembly_worker_teams,
                                          grout_gears=grout_gears,
                                          crane_num=1,
                                          activities=activities)
    # get sample data (decimals)
    smp_delivery_delay, smp_duration = get_randoms(seed)
    # set sample duration and delivery
    index = 0
    for activity in activities:
        activity.construction_site = construction_site1
        activity.completion_event = env.event()
        activity.component_delivered_event = env.event()
        # actual duration is set according to sample data
        activity.act_duration = math.ceil(smp_duration[index])
        # skd_delivery_time + chromosome representing receiving postponement + delivery delay
        # activity.skd_delivery_time = max(0, activity.es + start_correction[index])
        # scheduled delivery time is used as scheduled erection time
        activity.skd_delivery_time = activity.es + start_correction[index]
        # ensure the scheduled erection time non-negative
        if activity.skd_delivery_time < 0:
            activity.skd_delivery_time = 0
        # print("skd_delivery_time=",activity.es, start_correction[index], activity.skd_delivery_time)
        activity.act_delivery_time = activity.skd_delivery_time + max(0, math.ceil(smp_delivery_delay[index]))
        index += 1
    # the act_delivery_time has been preset, no need to process.
    counter = 1
    for activity in activities:
        if counter < 97:  # now, the statement is redundant as the number of activities is less than 96.
            env.process(activity.transport())
            counter += 1
    counter = 1
    for activity in activities:
        if counter < 97:
            env.process(activity.act())
            counter += 1
    env.run(until=2000)

    if debug:
        filename = 'Project process simulation' + str(seed) + '.xlsx'
        # print(filename)
        if os.path.exists(filename):
            os.remove(filename)

        wb = Workbook()
        # ws = wb.remove_sheet
        ws = wb.active
        title = ['name', 'es', 'duration', 'chromo.', 'skd_delivery_time', 'act_delivery_time',
                 'act_erection_start', 'act_duration', 'delivery delay',
                 'start delay', 'waiting cost', 'penalty for cmp. deli.', 'penalty for prj. deli.']
        ws.append(title)
    # print(env.now, construction_site1)
    total_waiting_cost = 0
    total_late_delivery_penalty = 0
    project_act_completion = 0
    counter = 0
    for activity in activities:
        # print('act Start', activity.name, activity.act_erection_start, activity.act_delivery_time)
        total_waiting_cost += \
            unit_waiting_penalty * (activity.act_erection_start - activity.act_delivery_time)
        total_late_delivery_penalty += \
            unit_late_delivery_penalty * (activity.act_delivery_time - activity.skd_delivery_time)
        #
        current_act_completion = activity.act_erection_start + activity.act_duration
        # keep updating project_act_completion
        project_act_completion = max(project_act_completion, current_act_completion)
        if debug:
            component = [activity.name, round(activity.es, 2), activity.duration,
                         round(start_correction[counter]),  # 4 -- order of the element
                         round(activity.skd_delivery_time, 2), round(activity.act_delivery_time, 2),  # 5 6
                         round(activity.act_erection_start, 2), round(activity.act_duration, 2),  # 7 8
                         round(activity.act_delivery_time - activity.skd_delivery_time, 2),  # 9
                         round(activity.act_erection_start - activity.es, 2),  # 10
                         round(unit_waiting_penalty * (activity.act_erection_start - activity.act_delivery_time), 2),
                         # 11
                         round(unit_late_delivery_penalty * (activity.act_delivery_time - activity.skd_delivery_time),
                               # 12
                               2),
                         ]
            ws.append(component)
        counter += 1
        # print(activity.name, round(activity.es, 2), activity.duration,
        #       round(activity.skd_delivery_time, 2), round(activity.act_delivery_time, 2),
        #       round(activity.act_erection_start, 2),
        #       activity.act_duration)
    project_late_completion_penalty \
        = max(0, (project_act_completion - project_deadline)) * unit_project_late_completion_penalty
    # print(delivery_time_offset)
    # print(total_waiting_cost - total_late_delivery_penalty + project_late_completion_penalty,
    #       total_waiting_cost, total_late_delivery_penalty, project_late_completion_penalty)
    total_cost = total_waiting_cost - total_late_delivery_penalty + project_late_completion_penalty
    # to output
    if debug:
        summary = ['', '', '',
                   '',
                   '', '',
                   '', '',
                   'T. Cost=',
                   '=K20-L20+M20',
                   '=SUM(K2:K19)',
                   '=SUM(L2:L19)',
                   project_late_completion_penalty,
                   max(0, (project_act_completion - project_deadline))
                   ]
        ws.append(summary)
        wb.save(filename)
        wb.close()
    if debug:
        # print('delivery_time_offset=', delivery_time_offset)
        # print('debug costs=', total_cost, total_waiting_cost, total_late_delivery_penalty,
        #       project_late_completion_penalty,
        #       project_act_completion, project_deadline)
        return total_waiting_cost, total_late_delivery_penalty, project_late_completion_penalty
    else:
        # print('total costs=', total_cost, total_waiting_cost, total_late_delivery_penalty,
        #       project_late_completion_penalty, project_act_completion, project_deadline)
        return total_cost


def get_randoms(seed):
    # print('seed=', seed)
    random_util = RandomUtil(seed, n_sf=Constants.N_ACTIVITY)
    # act_duration of walls of two types
    # act_duration = []
    # sample duration
    smp_duration = random_util.get_sf_erection_durations()
    # act_duration.extend(durations)
    # sample delivery delay
    smp_delivery_delay = random_util.get_component_delivery_delays()
    # print(act_delivery_delay)
    return smp_delivery_delay.tolist(), smp_duration


if __name__ == '__main__':
    # delivery_time_offset = [26, 25, 11, 6, 34, 60, 59, 68, 52, 74, 46, 34, 67, 68, 74, 100, 100, 100, 99, 100, 100, 35,
    #                         77, 58, 99, 100, 100,
    #                         83, 100, 100, 100, 100, 80, 100, 97, 100, 95, 94, 86, 78, 100, 91, 100, 100, 91, 94, 100,
    #                         100]
    # print(len(delivery_time_offset))
    # total_waiting_cost = get_cost_by_simulation(delivery_time_offset)
    # print(total_waiting_cost)
    #
    # delivery_time_offset = [54, 52, 33, 27, 58, 74, 76, 100, 100, 100, 85, 86, 94, 57,
    #                         87, 100, 82, 99, 77, 100, 100, 86, 100, 100, 97, 93, 100, 100,
    #                         88, 100, 100, 100, 100, 100, 43, 100, 100, 89, 82, 92, 99, 97,
    #                         100, 100, 100, 77, 69, 96]
    # print(len(delivery_time_offset))
    # total_waiting_cost = get_cost_by_simulation(delivery_time_offset)
    # print(total_waiting_cost)
    #
    # delivery_time_offset = [33, 16, 39, 43, 59, 59, 70, 75, 78, 71, 67, 46, 75, 90, 85, 100, 63, 94, 94, 100, 99,
    #                         100, 100, 100, 97, 90, 100, 82, 99, 97, 100, 85, 74, 100, 100, 100, 98, 89, 86, 100, 94, 65,
    #                         100, 100, 81, 97, 100, 100]
    # print(len(delivery_time_offset))
    # total_waiting_cost = get_cost_by_simulation(delivery_time_offset)
    # print(total_waiting_cost)

    print('base plan')
    # base plan
    delivery_time_offset = [0] * Constants.N_ACTIVITY
    for seed in range(1, 31):
        # act_delivery_delay, durations = get_randoms(seed=seed)
        # print(durations)
        total_waiting_cost = get_cost_with_simulation(start_correction=delivery_time_offset, seed=seed, debug=True)
        print(total_waiting_cost)
    print('####')
    # delivery_time_offset = [2, 3, 2, 3, 3, 3, 2, 1, 2, 3, 4, 3, 4, 4, 4, 4, 5, 5]
    # print(get_cost_with_simulation(delivery_time_offset=delivery_time_offset, seed=1))
    # delivery_time_offset = [1, 1, 2, 2, 3, 3, 2, 2, 4, 4, 5, 5, 5, 5, 6, 6, 7, 7]
    # print(get_cost_with_simulation(delivery_time_offset=delivery_time_offset, seed=1, debug=True))

    # delivery_time_offset = [0, 0, 2, 3, 3, 5, 4, 4, 4, 4, 5, 5, 5, 5, 6, 5, 6, 6]
    # print(get_cost_with_simulation(delivery_time_offset=delivery_time_offset, seed=1, debug=True))

    # delivery_time_offset = [0, 1, 2, 1, 3, 3, 2, 2, 3, 2, 3, 3, 3, 3, 4, 3, 4, 3]
    # print(get_cost_with_simulation(delivery_time_offset=delivery_time_offset, seed=1, debug=True))
